package io.kiki.sba.registry.store.jdbc.repository.impl;

import com.google.common.collect.Sets;
import io.kiki.sba.registry.common.model.Tuple;
import io.kiki.sba.registry.common.model.appmeta.InterfaceMapping;
import io.kiki.sba.registry.concurrent.CachedExecutor;
import io.kiki.sba.registry.store.api.config.DefaultCommonConfig;
import io.kiki.sba.registry.store.api.date.DateNowRepository;
import io.kiki.sba.registry.store.api.meta.RecoverConfig;
import io.kiki.sba.registry.store.api.repository.InterfaceAppsRepository;
import io.kiki.sba.registry.store.jdbc.constant.TableEnum;
import io.kiki.sba.registry.store.jdbc.domain.InterfaceAppsIndexDomain;
import io.kiki.sba.registry.store.jdbc.informer.BaseInformer;
import io.kiki.sba.registry.store.jdbc.mapper.InterfaceAppsIndexMapper;
import io.kiki.sba.registry.util.StringFormatter;
import io.kiki.sba.registry.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.Map.Entry;


public class InterfaceAppsJdbcRepository implements InterfaceAppsRepository, RecoverConfig {

    private static final Logger logger = LoggerFactory.getLogger(InterfaceAppsJdbcRepository.class);
    final Informer informer;
    private final CachedExecutor<Tuple<String, String>, Boolean> cachedExecutor = new CachedExecutor<>(1000 * 10);
    @Autowired
    private InterfaceAppsIndexMapper interfaceAppsIndexMapper;
    @Autowired
    private DefaultCommonConfig defaultCommonConfig;
    @Autowired
    private DateNowRepository dateNowRepository;
    private Set<String> dataCenters = Sets.newConcurrentHashSet();

    public InterfaceAppsJdbcRepository() {
        informer = new Informer();
    }

    @Override
    public InterfaceMapping getAppNames(String dataInfoId) {
        InterfaceAppsIndexContainer index = informer.getContainer();
        Map<String, InterfaceMapping> mappings = index.getAppMapping(dataInfoId);
        if (CollectionUtils.isEmpty(mappings)) {
            return new InterfaceMapping(-1);
        }

        long maxVersion = -1L;
        Set<String> apps = new HashSet<>();
        for (InterfaceMapping value : mappings.values()) {
            if (value.getNanosVersion() > maxVersion) {
                maxVersion = value.getNanosVersion();
            }
            apps.addAll(value.getApps());
        }
        InterfaceMapping ret = new InterfaceMapping(maxVersion, apps);

        return ret;
    }

    @Override
    public void register(String appName, Set<String> interfaceNames) {
        String localDataCenter = defaultCommonConfig.getDefaultClusterId();
        InterfaceAppsIndexContainer c = informer.getContainer();

        for (String interfaceName : interfaceNames) {
            if (c.containsName(localDataCenter, interfaceName, appName)) {
                continue;
            }
            refreshEntryToStorage(new InterfaceAppsIndexDomain(localDataCenter, interfaceName, appName));
        }
    }

    @Override
    public void renew(String interfaceName, String appName) {
        refreshEntryToStorage(new InterfaceAppsIndexDomain(defaultCommonConfig.getDefaultClusterId(), interfaceName, appName));
    }

    @Override
    public int cleanDeleted(Date beforeTime, int limit) {
        return interfaceAppsIndexMapper.cleanDeleted(defaultCommonConfig.getDefaultClusterId(), beforeTime, limit);
    }

    @Override
    public void startSynced() {
        StringUtil.checkNotEmpty(dataCenters, "dataCenters");

        // after set datacenters
        informer.setEnabled(true);
        informer.start();
    }

    @Override
    public void waitSynced() {
        informer.waitSynced();
    }

    protected void refreshEntryToStorage(InterfaceAppsIndexDomain interfaceAppsIndexDomain) {
        try {
            cachedExecutor.execute(new Tuple<>(interfaceAppsIndexDomain.getInterfaceName(), interfaceAppsIndexDomain.getAppName()), () -> {
                if (interfaceAppsIndexMapper.update(interfaceAppsIndexDomain) == 0) {
                    interfaceAppsIndexMapper.replace(interfaceAppsIndexDomain);
                }
                logger.info("insert interface app mapping {}=>{} succeed,entry:{}", interfaceAppsIndexDomain.getInterfaceName(), interfaceAppsIndexDomain.getAppName(), interfaceAppsIndexDomain);
                return true;
            });
        } catch (Exception e) {
            logger.error("refresh to db failed: ", e);
            throw new RuntimeException(StringFormatter.format("refresh to db failed: {}", e.getMessage()));
        }
    }


    void cleanCache() {
        cachedExecutor.clean();
    }

    public long getDataVersion() {
        return informer.getLastLoadId();
    }

    @Override
    public Map<String, Map<String, InterfaceMapping>> allServiceMapping() {
        return informer.getContainer().allServiceMapping();
    }

    @Override
    public String tableName() {
        return TableEnum.interface_app_index.getTableName();
    }

    @Override
    public Set<String> dataCenters() {
        return new HashSet<>(dataCenters);
    }

    @Override
    public synchronized void setDataCenters(Set<String> dataCenters) {

        if (!this.dataCenters.equals(dataCenters)) {
            // wakeup list loop to rebuild container
            logger.info("dataCenters change from {} to {}", this.dataCenters, dataCenters);
            this.dataCenters = dataCenters;
            informer.listWakeup();
        }
    }


    public InterfaceAppsJdbcRepository setDefaultCommonConfig(DefaultCommonConfig defaultCommonConfig) {
        this.defaultCommonConfig = defaultCommonConfig;
        return this;
    }

    interface ConflictCallback {
        void callback(InterfaceAppsIndexContainer current, InterfaceAppsIndexContainer newContainer);
    }

    class Informer extends BaseInformer<InterfaceAppsIndexDomain, InterfaceAppsIndexContainer> {
        private ConflictCallback conflictCallback;

        public Informer() {
            super("InterfaceAppsIndex");
        }

        @Override
        protected InterfaceAppsIndexContainer containerFactory() {
            return new InterfaceAppsIndexContainer();
        }

        @Override
        protected List<InterfaceAppsIndexDomain> listFromStorage(long start, int limit) {
            List<InterfaceAppsIndexDomain> res = interfaceAppsIndexMapper.queryLargeThan(dataCenters, start, limit);
            logger.info("query apps by interface, dataCenters:{}, start:{}, limit:{}, res:{}", dataCenters, start, limit, res.size());
            return res;
        }

        @Override
        protected Date getNow() {
            return dateNowRepository.getNow();
        }


        public void setConflictCallback(ConflictCallback runnable) {
            conflictCallback = runnable;
        }

        @Override
        protected void preList(InterfaceAppsIndexContainer newContainer) {
            InterfaceAppsIndexContainer current = this.container;
            for (String interfaceName : current.interfaces()) {
                Map<String, InterfaceMapping> newMappings = newContainer.getAppMapping(interfaceName);
                Map<String, InterfaceMapping> currentMappings = current.getAppMapping(interfaceName);
                for (Entry<String, InterfaceMapping> entry : currentMappings.entrySet()) {
                    InterfaceMapping newMapping = newMappings.get(entry.getKey());
                    InterfaceMapping currentMapping = entry.getValue();
                    if (newMapping == null) {
                        continue;
                    }
                    if (newMapping.getNanosVersion() < currentMapping.getNanosVersion() || (newMapping.getNanosVersion() == currentMapping.getNanosVersion() && !newMapping.getApps().equals(currentMapping.getApps()))) {
                        if (conflictCallback != null) {
                            conflictCallback.callback(current, newContainer);
                        }
                        logger.error("version conflict dataCenter: {}, current: {}, new: {}", entry.getKey(), currentMapping, newMapping);
                    }
                }
            }
        }
    }
}
